Prediction with RFM

The Charles Book Club case was derived, with the assistance of Ms. Vinni Bhandari, from The Bookbinders Club, a Case Study in Database Marketing, prepared by Nissan Levin and Jacob Zahavi, Tel Aviv University

The Charles Book Club (CBC) was established in December 1986 on the premise that a book club could differentiate itself through a deep understanding of its customer base and by delivering uniquely tailored offerings. CBC focused on selling specialty books by direct marketing through a variety of channels, including media advertising (TV, magazines, newspapers) and mailing. CBC is strictly a distributor and does not publish any of the books that it sells. In line with its commitment to understanding its customer base, CBC built and maintained a detailed database about its club members. Upon enrollment, readers were required to fill out an insert and mail it to CBC. Through this process, CBC created an active database of 500,000 readers; most were acquired through advertising in specialty magazines.

Historically, book clubs offered their readers different types of membership programs. Two common membership programs are the continuity and negative option programs, which are both extended contractual relationships between the club and its members. Under a continuity program, a reader signs up by accepting an offer of several books for just a few dollars (plus shipping and handling) and an agreement to receive a shipment of one or two books each month thereafter at more-standard pricing. The continuity program is most common in the children’s book market, where parents are willing to delegate the rights to the book club to make a selection, and much of the club’s prestige depends on the quality of its selections.

In a negative option program, readers get to select how many and which additional books they would like to receive. However, the club’s selection of the month is delivered to them automatically unless they specifically mark “no” on their order form by a deadline date. Negative option programs sometimes result in customer dissatisfaction and always give rise to significant mailing and processing costs.

In an attempt to combat these trends, some book clubs have begun to offer books on a positive option basis, but only to specific segments of their customer base that are likely to be receptive to specific offers. Rather than expanding the volume and coverage of mailings, some book clubs are beginning to use database-marketing techniques to target customers more accurately. Information contained in their databases is used to identify who is most likely to be interested in a specific offer. This information enables clubs to design special programs carefully tailored to meet their customer segments’ varying needs.

The Problem

CBC sent mailings to its club members each month containing the latest offerings. On the surface, CBC appeared very successful: mailing volume was increasing, book selection was diversifying and growing,and their customer database was increasing. However, their bottom-line profits were falling. The decreasing profits led CBC to revisit their original plan of using database marketing to improve mailing yields and to stay profitable.

A Possible Solution

CBC embraced the idea of deriving intelligence from their data to allow them to know their customers better and enable multiple targeted campaigns where each target audience would receive appropriate mailings. CBC’s management decided to focus its efforts on the most profitable customers and prospects, and to design targeted marketing strategies to best reach them. The two processes they had in place were:

1. Customer acquisition:

  • New members would be acquired by advertising in specialty magazines, newspapers, and on TV.

  • Direct mailing and telemarketing would contact existing club members.

  • Every new book would be offered to club members before general advertising.

2. Data collection:

  • All customer responses would be recorded and maintained in the database.
  • Any information not being collected that is critical would be requested from the customer.

For each new title, they decided to use a two-step approach:

  1. Conduct a market test involving a random sample of 4000 customers from the database to enable analysis of customer responses. The analysis would create and calibrate response models for the current book offering.

  2. Based on the response models, compute a score for each customer in the database. Use this score and a cutoff value to extract a target customer list for direct-mail promotion.

Targeting promotions was considered to be of prime importance. Other opportunities to create successful marketing campaigns based on customer behavior data (returns, inactivity, complaints, compliments, etc.) would be addressed by CBC at a later stage.

Background Information

A new title, The Art History of Florence, is ready for release. CBC sent a test mailing to a random sample of 4000 customers from its customer base. The customer responses have been collated with past purchase data. The dataset was randomly partitioned into three parts:

  • Training Data (1800 customers): initial data to be used to fit models,
  • Validation Data (1400 customers): holdout data used to compare the performance of different models,
  • and Test Data (800 customers): data to be used only after a final model has been selected to estimate the probable performance of the model when it is deployed.

Each row (or case) in the spreadsheet (other than the header) corresponds to one market test customer. Each column is a variable, with the header row giving the name of the variable.

Recency - Frequency - Monetary Analysis

The segmentation process in database marketing aims to partition customers in a list of prospects into homogeneous groups (segments) that are similar with respect to buying behavior. The homogeneity criterion we need for segmentation is the propensity to purchase the offering. However, since we cannot measure this attribute, we use variables that are plausible indicators of this propensity.

In the direct marketing business, the most commonly used variables are the RFM variables:

  • R = recency, time since last purchase
  • F = frequency, number of previous purchases from the company over a period
  • M = monetary, amount of money spent on the company’s products over a period

The assumption is that the more recent the last purchase, the more products bought from the company in the past, and the more money spent in the past buying the company’s products, the more likely the customer is to purchase the product offered.

The 1800 observations in the dataset were divided into recency, frequency, and monetary categories as follows:

  • Recency:
Recency Recode
0–2 months Rcode = 1
3–6 months Rcode = 2
7–12 months Rcode = 3
13 months and up Rcode = 4
  • Frequency:
Frequency Recode
1 book Fcode = 1
2 books Fcode = 2
3 books Fcode = 3
  • Monetary:
Monetary Recode
0 – 25 Mcode = 1
26–50 Mcode = 2
51–100 Mcode = 3
101–200 Mcode = 4
201 and up Mcode = 5
Note: Montary values are denoted in USD

Assignment

Partition the data into training (60%) and validation (40%). Use seed = 1.

What is the response rate for the training data customers taken as a whole? What is the response rate for each of the 4×5×3 = 60 combinations of RFM categories? Which combinations have response rates in the training data that are above the overall response in the training data?

Before we answer the question, let us explore the data set:

Rows: 4,000
Columns: 24
$ Seq.             <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, ...
$ ID.              <int> 25, 29, 46, 47, 51, 60, 61, 79, 81, 90, 95, 100...
$ Gender           <int> 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1,...
$ M                <int> 297, 128, 138, 228, 257, 145, 190, 187, 252, 24...
$ R                <int> 14, 8, 22, 2, 10, 6, 16, 14, 10, 6, 2, 2, 4, 14...
$ F                <int> 2, 2, 7, 1, 1, 2, 1, 1, 1, 3, 4, 3, 1, 1, 2, 9,...
$ FirstPurch       <int> 22, 10, 56, 2, 10, 12, 16, 14, 10, 20, 20, 18, ...
$ ChildBks         <int> 0, 0, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 2,...
$ YouthBks         <int> 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
$ CookBks          <int> 1, 0, 2, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 3,...
$ DoItYBks         <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
$ RefBks           <int> 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,...
$ ArtBks           <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1,...
$ GeogBks          <int> 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2,...
$ ItalCook         <int> 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,...
$ ItalAtlas        <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,...
$ ItalArt          <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,...
$ Florence         <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,...
$ Related.Purchase <int> 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 6,...
$ Mcode            <int> 5, 4, 4, 5, 5, 4, 4, 4, 5, 5, 5, 5, 5, 4, 5, 5,...
$ Rcode            <int> 4, 3, 4, 1, 3, 2, 4, 4, 3, 2, 1, 1, 2, 4, 2, 4,...
$ Fcode            <int> 2, 2, 3, 1, 1, 2, 1, 1, 1, 3, 3, 3, 1, 1, 2, 3,...
$ Yes_Florence     <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,...
$ No_Florence      <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1,...

First, we will convert the response variable to factor class.

data$Yes_Florence <- factor(data$Yes_Florence, labels = c("No","Yes"),levels = c(0:1))
data$No_Florence <-factor(data$No_Florence, labels = c("No","Yes"),levels = c(0:1))

Second, we would need to compute RFM score by merging three scores into one cell.

# Calculating RFM score
data$RFM_score<- paste(data$Rcode,data$Fcode,data$Mcode)
data$RFM_score <- gsub(" ","",data$RFM_score)
data$RFM_score<-as.factor(data$RFM_score)
data[1:10,"RFM_score"]
 [1] 425 324 434 115 315 224 414 414 315 235
51 Levels: 111 112 113 114 115 122 123 124 125 132 133 134 135 211 ... 435

Now we will proceed with data partition. For that we will use sample() function, where we indicate rownames and the number of randomly selected row numbers we want to separate from the remaining data set. In our case, out of 4000 rows (Customers), we will randomly assign 2800 to train data set, and the rest to test data.

# Train data
set.seed(1)
trainIndex <- caret::createDataPartition(data$Florence, p = .7, 
                                  list = FALSE, 
                                  times = 1)
train_data <- data[ trainIndex,]

The remaining part of the data set will be assigned to the validation data set.

# Validation set
validation_data  <- data[-trainIndex,]

Visaul exploration

It would be beneficial to inspect relationship between recency, frequency and monetary value in the whole data set before we continue. A quite convenient way to do it is a heatmap. There we can plot all three variables at the same time and inspect the monetary value (= amount spend in the time frame observed) of each customer based on his/her frequency and recency.

Unsuprisingly, customers who purchased more frequently generated more revenue compared to those who visited less frequently. However, customers who spent the most are not the most recent ones (= last purchase in 0-2): the heaviest spenders are the frequent ones, but they haven’t made a purchase 3 to 6 months. Usually, the customers who visited in the recent past (0-2 months) are more likely to return compared to those who made a purchase some time ago as most of those could potentially be lost customers. As such, higher revenue would be associated with most recent visits, but we see that is not really the case here. This could be related to the nature of books as products and the fact that they are not frequently purchased items such as daily products for instance. Nevertheless, it would definitively be worth to consider giving incentives to these customers who spent the most, but the company has not heard of them for 3-6 months.

A legit question for better understanding of our target group would be about the difference in recency of customers who responded to advertising of “Florence”.

data %>%
  rename(Revenue=M,
         Response=Yes_Florence) %>%
  ggplot(aes(x=Response,y=F,fill=Response))+
  geom_boxplot()+
  labs(x="Response to Campaign",y="Frequency (Number of Purchases)",title = "How Frequent Are Customers Interested in 'Florence'?",
       subtitle = "Boxplots depicting frequency of respondents and non-respondents")+
  stat_summary(fun.y=mean, geom="point", shape=20, size=10, color="red", fill="red") +
  theme_bw()
`fun.y` is deprecated. Use `fun` instead.

There seem to be difference in means of the two groups. Non-respondents are somewhat less frequent customers, while respondents belong to more frequent customers. Let us see about their recency.

Customers who responded to the “Florence” campaign have on average lower recency than customers who did not respond. All in all, we can conclude that customers who responded to the campaign are, on average, slightly more frequent and recent than customers who did not respond to campaign.

Response rate in training data

Now we can start with addressing the first question: what is the response rate for training data customers taken as whole?

Let us now inspect the response rate for training data customers.

Response rate of customers from the training data is around 9%.

[1] 0.08571429

Prediction of response rate - single cluster

Now we would need to inspect response rates from all 60 (possible) combinations, and compare them with the overall one.

The following RFM scores indicate response rate higher than 9%:

Suppose that we decide to send promotional mail only to the “above average” RFM combinations identified in part. Now we should compute the response rate in the validation data using these “above average” combinations.

Predicted response rate from the training data set is said to be at around 15.1%, while the true response rate taken from validation set is at 10.3%. From comparison of true response rate in the validation data set and the predicted response rate from the training data set it seems that the latter pretty much deviates from the former. In the next section we will split customers from the training set into 3 clusters, and again compare predicted with the true response rate.

Prediction of response rate - 3 clusters

Let us now segment our customers in 3 different segments:

  • Segment 1: RFM combinations that have response rates that exceed twice the overall response rate
  • Segment 2: RFM combinations that exceed the overall response rate but do not exceed twice that rate
  • Segment 3: the remaining RFM combinations

We classified RFM scores based on response rate into 3 segments. Now we will identify customers with those RFM scores in the validation set, and compare predicted/expected response rate from the training data set with the actual response rate.

By visual inspection we could see that predicted response rates are above the true ones in two out of 3 segments. Customers who had response rate at least twice the initial response rate (9%) were expected to have around 25% response rate, but the true response rate is at significantly lower 9.5% response rate. Similarly, the RFM prediction in case of customers who had response rate between 9 and 18% was slightly inaccurate as well(predicted 14.1 % vs 10.4% true response rate). The only case where true response rate exceeded the predicted response rate is the segment with the response rate below 9%.

However, in order to find out how well our model performed, we will create a gain chart. It helps us determine how effectively we can proceed with our campaign. We aim at selecting a relatively small number of customers and getting a relatively large portion of respondents. For a given number of customers expressed in percentages, the gain curve value on the y-axis will shows us how much better we are doing compared to random choice of customers.

Based on the gain chart, prediction based on our RFM model performs a bit better than baseline, i.e. random guessing. More specifically, if we select top 20% cases based on our model, we would attract 29% of the target customers, i.e. customers who would respond to our marketing campaign.

As the graph is interactive, you are able to check any other percentage you wish. Although we managed to create a model that performs better than just guessing, in the further analyses we will try out other approaches, such as logistic regression, that may provide us better results.

Reference

  • Shmueli, G., Bruce, P. C., Yahav, I., Patel, N. R., & Lichtendahl, K. C. (2018). Data mining for business analytics: Concepts, techniques, and applications in R.
LS0tDQp0aXRsZTogJ1ByZWRpY3RpdmUgUkZNOiBDaGFybGVzIEJvb2sgQ2x1YicNCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBrZWVwX21kOiB0cnVlDQogICAgdG9jOiB5ZXMNCiAgICBkZl9wcmludDogcGFnZWQNCiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdA0KICBwZGZfZG9jdW1lbnQ6DQogICAgdG9jOiB5ZXMNCi0tLQ0KDQojIFByZWRpY3Rpb24gd2l0aCBSRk0NCg0KYGBge3IsZWNobz1GQUxTRSwgZmlnLmFsaWduPSdjZW50ZXInLCBvdXQud2lkdGg9IjUwJSIsZmlnLmNhcD0iRm90byBmcm9tIHBleGVscy5jb20ifQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoIkdyYXBoaWNzL2Jvb2tjbHViLmpwZyIpDQpgYGANCg0KDQoqVGhlIENoYXJsZXMgQm9vayBDbHViIGNhc2Ugd2FzIGRlcml2ZWQsIHdpdGggdGhlIGFzc2lzdGFuY2Ugb2YgTXMuIFZpbm5pIEJoYW5kYXJpLCBmcm9tIFRoZSBCb29rYmluZGVycyBDbHViLCBhIENhc2UgU3R1ZHkgaW4gRGF0YWJhc2UgTWFya2V0aW5nLCBwcmVwYXJlZCBieSBOaXNzYW4gTGV2aW4gYW5kIEphY29iIFphaGF2aSwgVGVsIEF2aXYgVW5pdmVyc2l0eSogDQoNCjxkaXYgc3R5bGU9InRleHQtYWxpZ246IGp1c3RpZnkiPiANCg0KVGhlIENoYXJsZXMgQm9vayBDbHViIChDQkMpIHdhcyBlc3RhYmxpc2hlZCBpbiBEZWNlbWJlciAxOTg2IG9uIHRoZSBwcmVtaXNlIHRoYXQgYSBib29rIGNsdWIgY291bGQgZGlmZmVyZW50aWF0ZSBpdHNlbGYgdGhyb3VnaCBhIGRlZXAgdW5kZXJzdGFuZGluZyBvZiBpdHMgY3VzdG9tZXIgYmFzZSBhbmQgYnkgZGVsaXZlcmluZyB1bmlxdWVseSB0YWlsb3JlZCBvZmZlcmluZ3MuDQpDQkMgZm9jdXNlZCBvbiBzZWxsaW5nIHNwZWNpYWx0eSBib29rcyBieSBkaXJlY3QgbWFya2V0aW5nIHRocm91Z2ggYSB2YXJpZXR5IG9mIGNoYW5uZWxzLCBpbmNsdWRpbmcgbWVkaWEgYWR2ZXJ0aXNpbmcgKFRWLCBtYWdhemluZXMsIG5ld3NwYXBlcnMpIGFuZCBtYWlsaW5nLiBDQkMgaXMgc3RyaWN0bHkgYSBkaXN0cmlidXRvciBhbmQgZG9lcyBub3QgcHVibGlzaCBhbnkgb2YgdGhlIGJvb2tzIHRoYXQgaXQgc2VsbHMuIEluIGxpbmUgd2l0aCBpdHMgY29tbWl0bWVudCB0byB1bmRlcnN0YW5kaW5nIGl0cyBjdXN0b21lciBiYXNlLCBDQkMgYnVpbHQgYW5kIG1haW50YWluZWQgYSBkZXRhaWxlZCBkYXRhYmFzZSBhYm91dCBpdHMgY2x1YiBtZW1iZXJzLiBVcG9uIGVucm9sbG1lbnQsIHJlYWRlcnMgd2VyZSByZXF1aXJlZCB0byBmaWxsIG91dCBhbiBpbnNlcnQgYW5kIG1haWwgaXQgdG8gQ0JDLiBUaHJvdWdoIHRoaXMgcHJvY2VzcywgQ0JDIGNyZWF0ZWQgYW4gYWN0aXZlIGRhdGFiYXNlIG9mIDUwMCwwMDAgcmVhZGVyczsgbW9zdCB3ZXJlIGFjcXVpcmVkIHRocm91Z2ggYWR2ZXJ0aXNpbmcgaW4gc3BlY2lhbHR5IG1hZ2F6aW5lcy4NCg0KSGlzdG9yaWNhbGx5LCBib29rIGNsdWJzIG9mZmVyZWQgdGhlaXIgcmVhZGVycyBkaWZmZXJlbnQgdHlwZXMgb2YgbWVtYmVyc2hpcCBwcm9ncmFtcy4gVHdvIGNvbW1vbiBtZW1iZXJzaGlwIHByb2dyYW1zIGFyZSB0aGUgY29udGludWl0eSBhbmQgbmVnYXRpdmUgb3B0aW9uIHByb2dyYW1zLCB3aGljaCBhcmUgYm90aCBleHRlbmRlZCBjb250cmFjdHVhbCByZWxhdGlvbnNoaXBzIGJldHdlZW4gdGhlIGNsdWIgYW5kIGl0cyBtZW1iZXJzLiBVbmRlciBhIGNvbnRpbnVpdHkgcHJvZ3JhbSwgYSByZWFkZXIgc2lnbnMgdXAgYnkgYWNjZXB0aW5nIGFuIG9mZmVyIG9mIHNldmVyYWwgYm9va3MgZm9yIGp1c3QgYSBmZXcgZG9sbGFycyAocGx1cyBzaGlwcGluZyBhbmQgaGFuZGxpbmcpIGFuZCBhbiBhZ3JlZW1lbnQgdG8gcmVjZWl2ZSBhIHNoaXBtZW50IG9mIG9uZSBvciB0d28gYm9va3MgZWFjaCBtb250aCB0aGVyZWFmdGVyIGF0IG1vcmUtc3RhbmRhcmQgcHJpY2luZy4gVGhlIGNvbnRpbnVpdHkgcHJvZ3JhbSBpcyBtb3N0IGNvbW1vbiBpbiB0aGUgY2hpbGRyZW7igJlzIGJvb2sgbWFya2V0LCB3aGVyZSBwYXJlbnRzIGFyZSB3aWxsaW5nIHRvIGRlbGVnYXRlIHRoZSByaWdodHMgdG8gdGhlIGJvb2sgY2x1YiB0byBtYWtlIGEgc2VsZWN0aW9uLCBhbmQgbXVjaCBvZiB0aGUgY2x1YuKAmXMgcHJlc3RpZ2UgZGVwZW5kcyBvbiB0aGUgcXVhbGl0eSBvZiBpdHMgc2VsZWN0aW9ucy4NCg0KSW4gYSBuZWdhdGl2ZSBvcHRpb24gcHJvZ3JhbSwgcmVhZGVycyBnZXQgdG8gc2VsZWN0IGhvdyBtYW55IGFuZCB3aGljaCBhZGRpdGlvbmFsIGJvb2tzIHRoZXkgd291bGQgbGlrZSB0byByZWNlaXZlLiBIb3dldmVyLCB0aGUgY2x1YuKAmXMgc2VsZWN0aW9uIG9mIHRoZSBtb250aCBpcyBkZWxpdmVyZWQgdG8gdGhlbSBhdXRvbWF0aWNhbGx5IHVubGVzcyB0aGV5IHNwZWNpZmljYWxseSBtYXJrIOKAnG5v4oCdIG9uIHRoZWlyIG9yZGVyIGZvcm0gYnkgYSBkZWFkbGluZSBkYXRlLiBOZWdhdGl2ZSBvcHRpb24gcHJvZ3JhbXMgc29tZXRpbWVzIHJlc3VsdCBpbiBjdXN0b21lciBkaXNzYXRpc2ZhY3Rpb24gYW5kIGFsd2F5cyBnaXZlIHJpc2UgdG8gc2lnbmlmaWNhbnQgbWFpbGluZyBhbmQgcHJvY2Vzc2luZyBjb3N0cy4NCg0KSW4gYW4gYXR0ZW1wdCB0byBjb21iYXQgdGhlc2UgdHJlbmRzLCBzb21lIGJvb2sgY2x1YnMgaGF2ZSBiZWd1biB0byBvZmZlciBib29rcyBvbiBhIHBvc2l0aXZlIG9wdGlvbiBiYXNpcywgYnV0IG9ubHkgdG8gc3BlY2lmaWMgc2VnbWVudHMgb2YgdGhlaXIgY3VzdG9tZXIgYmFzZSB0aGF0IGFyZSBsaWtlbHkgdG8gYmUgcmVjZXB0aXZlIHRvIHNwZWNpZmljIG9mZmVycy4gUmF0aGVyIHRoYW4gZXhwYW5kaW5nIHRoZSB2b2x1bWUgYW5kIGNvdmVyYWdlIG9mIG1haWxpbmdzLCBzb21lIGJvb2sgY2x1YnMgYXJlIGJlZ2lubmluZyB0byB1c2UgZGF0YWJhc2UtbWFya2V0aW5nIHRlY2huaXF1ZXMgdG8gdGFyZ2V0IGN1c3RvbWVycyBtb3JlIGFjY3VyYXRlbHkuIEluZm9ybWF0aW9uIGNvbnRhaW5lZCBpbiB0aGVpciBkYXRhYmFzZXMgaXMgdXNlZCB0byBpZGVudGlmeSB3aG8gaXMgbW9zdCBsaWtlbHkgdG8gYmUgaW50ZXJlc3RlZCBpbiBhIHNwZWNpZmljIG9mZmVyLiBUaGlzIGluZm9ybWF0aW9uIGVuYWJsZXMgY2x1YnMgdG8gZGVzaWduIHNwZWNpYWwgcHJvZ3JhbXMNCmNhcmVmdWxseSB0YWlsb3JlZCB0byBtZWV0IHRoZWlyIGN1c3RvbWVyIHNlZ21lbnRz4oCZIHZhcnlpbmcgbmVlZHMuDQoNCg0KIyMgVGhlIFByb2JsZW0NCg0KQ0JDIHNlbnQgbWFpbGluZ3MgdG8gaXRzIGNsdWIgbWVtYmVycyBlYWNoIG1vbnRoIGNvbnRhaW5pbmcgdGhlIGxhdGVzdCBvZmZlcmluZ3MuIE9uIHRoZSBzdXJmYWNlLCBDQkMgYXBwZWFyZWQgdmVyeSBzdWNjZXNzZnVsOiBtYWlsaW5nIHZvbHVtZSB3YXMgaW5jcmVhc2luZywgYm9vayBzZWxlY3Rpb24gd2FzIGRpdmVyc2lmeWluZyBhbmQgZ3Jvd2luZyxhbmQgdGhlaXIgY3VzdG9tZXIgZGF0YWJhc2Ugd2FzIGluY3JlYXNpbmcuIEhvd2V2ZXIsIHRoZWlyIGJvdHRvbS1saW5lIHByb2ZpdHMgd2VyZSBmYWxsaW5nLiBUaGUgZGVjcmVhc2luZyBwcm9maXRzIGxlZCBDQkMgdG8gcmV2aXNpdCB0aGVpciBvcmlnaW5hbCBwbGFuIG9mIHVzaW5nIGRhdGFiYXNlIG1hcmtldGluZyB0byBpbXByb3ZlIG1haWxpbmcgeWllbGRzIGFuZCB0byBzdGF5IHByb2ZpdGFibGUuDQoNCg0KIyMgQSBQb3NzaWJsZSBTb2x1dGlvbg0KDQpDQkMgZW1icmFjZWQgdGhlIGlkZWEgb2YgZGVyaXZpbmcgaW50ZWxsaWdlbmNlIGZyb20gdGhlaXIgZGF0YSB0byBhbGxvdyB0aGVtIHRvIGtub3cgdGhlaXIgY3VzdG9tZXJzIGJldHRlciBhbmQgZW5hYmxlIG11bHRpcGxlIHRhcmdldGVkIGNhbXBhaWducyB3aGVyZSBlYWNoIHRhcmdldCBhdWRpZW5jZSB3b3VsZCByZWNlaXZlIGFwcHJvcHJpYXRlIG1haWxpbmdzLiBDQkPigJlzIG1hbmFnZW1lbnQgZGVjaWRlZCB0byBmb2N1cyBpdHMgZWZmb3J0cyBvbiB0aGUgbW9zdCBwcm9maXRhYmxlIGN1c3RvbWVycyBhbmQgcHJvc3BlY3RzLCBhbmQgdG8gZGVzaWduIHRhcmdldGVkIG1hcmtldGluZyBzdHJhdGVnaWVzIHRvIGJlc3QgcmVhY2ggdGhlbS4gVGhlIHR3byBwcm9jZXNzZXMgdGhleSBoYWQgaW4gcGxhY2Ugd2VyZToNCg0KKioxLiBDdXN0b21lciBhY3F1aXNpdGlvbjoqKg0KDQoqIE5ldyBtZW1iZXJzIHdvdWxkIGJlIGFjcXVpcmVkIGJ5IGFkdmVydGlzaW5nIGluIHNwZWNpYWx0eSBtYWdhemluZXMsIG5ld3NwYXBlcnMsIGFuZCBvbiBUVi4NCg0KKiBEaXJlY3QgbWFpbGluZyBhbmQgdGVsZW1hcmtldGluZyB3b3VsZCBjb250YWN0IGV4aXN0aW5nIGNsdWINCm1lbWJlcnMuDQoqIEV2ZXJ5IG5ldyBib29rIHdvdWxkIGJlIG9mZmVyZWQgdG8gY2x1YiBtZW1iZXJzIGJlZm9yZSBnZW5lcmFsDQphZHZlcnRpc2luZy4NCg0KKioyLiBEYXRhIGNvbGxlY3Rpb246KioNCg0KKiBBbGwgY3VzdG9tZXIgcmVzcG9uc2VzIHdvdWxkIGJlIHJlY29yZGVkIGFuZCBtYWludGFpbmVkIGluIHRoZQ0KZGF0YWJhc2UuDQoqIEFueSBpbmZvcm1hdGlvbiBub3QgYmVpbmcgY29sbGVjdGVkIHRoYXQgaXMgY3JpdGljYWwgd291bGQgYmUgcmVxdWVzdGVkDQpmcm9tIHRoZSBjdXN0b21lci4NCg0KRm9yIGVhY2ggbmV3IHRpdGxlLCB0aGV5IGRlY2lkZWQgdG8gdXNlIGEgdHdvLXN0ZXAgYXBwcm9hY2g6DQoNCjEuIENvbmR1Y3QgYSBtYXJrZXQgdGVzdCBpbnZvbHZpbmcgYSByYW5kb20gc2FtcGxlIG9mIDQwMDAgY3VzdG9tZXJzIGZyb20NCnRoZSBkYXRhYmFzZSB0byBlbmFibGUgYW5hbHlzaXMgb2YgY3VzdG9tZXIgcmVzcG9uc2VzLiBUaGUgYW5hbHlzaXMgd291bGQNCmNyZWF0ZSBhbmQgY2FsaWJyYXRlIHJlc3BvbnNlIG1vZGVscyBmb3IgdGhlIGN1cnJlbnQgYm9vayBvZmZlcmluZy4NCg0KMi4gQmFzZWQgb24gdGhlIHJlc3BvbnNlIG1vZGVscywgY29tcHV0ZSBhIHNjb3JlIGZvciBlYWNoIGN1c3RvbWVyIGluIHRoZQ0KZGF0YWJhc2UuIFVzZSB0aGlzIHNjb3JlIGFuZCBhIGN1dG9mZiB2YWx1ZSB0byBleHRyYWN0IGEgdGFyZ2V0IGN1c3RvbWVyDQpsaXN0IGZvciBkaXJlY3QtbWFpbCBwcm9tb3Rpb24uDQoNClRhcmdldGluZyBwcm9tb3Rpb25zIHdhcyBjb25zaWRlcmVkIHRvIGJlIG9mIHByaW1lIGltcG9ydGFuY2UuIE90aGVyDQpvcHBvcnR1bml0aWVzIHRvIGNyZWF0ZSBzdWNjZXNzZnVsIG1hcmtldGluZyBjYW1wYWlnbnMgYmFzZWQgb24gY3VzdG9tZXINCmJlaGF2aW9yIGRhdGEgKHJldHVybnMsIGluYWN0aXZpdHksIGNvbXBsYWludHMsIGNvbXBsaW1lbnRzLCBldGMuKSB3b3VsZCBiZQ0KYWRkcmVzc2VkIGJ5IENCQyBhdCBhIGxhdGVyIHN0YWdlLg0KDQoNCiMjIEJhY2tncm91bmQgSW5mb3JtYXRpb24gDQoNCkEgbmV3IHRpdGxlLCBUaGUgQXJ0IEhpc3Rvcnkgb2YgRmxvcmVuY2UsIGlzIHJlYWR5IGZvciByZWxlYXNlLiBDQkMgc2VudCBhIHRlc3QgbWFpbGluZyB0byBhIHJhbmRvbSBzYW1wbGUgb2YgNDAwMCBjdXN0b21lcnMgZnJvbSBpdHMgY3VzdG9tZXIgYmFzZS4gVGhlIGN1c3RvbWVyIHJlc3BvbnNlcyBoYXZlIGJlZW4gY29sbGF0ZWQgd2l0aCBwYXN0IHB1cmNoYXNlIGRhdGEuIFRoZSBkYXRhc2V0IHdhcyByYW5kb21seSBwYXJ0aXRpb25lZCBpbnRvIHRocmVlIHBhcnRzOiANCg0KKiBUcmFpbmluZyBEYXRhICgxODAwIGN1c3RvbWVycyk6IGluaXRpYWwgZGF0YSB0byBiZSB1c2VkIHRvIGZpdCBtb2RlbHMsDQoqIFZhbGlkYXRpb24gRGF0YSAoMTQwMCBjdXN0b21lcnMpOiBob2xkb3V0IGRhdGEgdXNlZCB0byBjb21wYXJlIHRoZSBwZXJmb3JtYW5jZSBvZiBkaWZmZXJlbnQgbW9kZWxzLA0KKiBhbmQgVGVzdCBEYXRhICg4MDAgY3VzdG9tZXJzKTogZGF0YSB0byBiZSB1c2VkIG9ubHkgYWZ0ZXIgYSBmaW5hbCBtb2RlbCBoYXMgYmVlbiBzZWxlY3RlZCB0byBlc3RpbWF0ZSB0aGUgcHJvYmFibGUgcGVyZm9ybWFuY2Ugb2YgdGhlIG1vZGVsIHdoZW4gaXQgaXMgZGVwbG95ZWQuDQoNCkVhY2ggcm93IChvciBjYXNlKSBpbiB0aGUgc3ByZWFkc2hlZXQgKG90aGVyIHRoYW4gdGhlIGhlYWRlcikgY29ycmVzcG9uZHMgdG8gb25lIG1hcmtldCB0ZXN0IGN1c3RvbWVyLiBFYWNoIGNvbHVtbiBpcyBhIHZhcmlhYmxlLCB3aXRoIHRoZSBoZWFkZXIgcm93IGdpdmluZyB0aGUgbmFtZSBvZiB0aGUgdmFyaWFibGUuDQoNCiMjIFJlY2VuY3kgLSBGcmVxdWVuY3kgLSBNb25ldGFyeSBBbmFseXNpcw0KDQpUaGUgc2VnbWVudGF0aW9uIHByb2Nlc3MgaW4gZGF0YWJhc2UgbWFya2V0aW5nIGFpbXMgdG8gcGFydGl0aW9uIGN1c3RvbWVycyBpbiBhIGxpc3Qgb2YgcHJvc3BlY3RzIGludG8gaG9tb2dlbmVvdXMgZ3JvdXBzIChzZWdtZW50cykgdGhhdCBhcmUgc2ltaWxhciB3aXRoIHJlc3BlY3QgdG8gYnV5aW5nIGJlaGF2aW9yLiBUaGUgaG9tb2dlbmVpdHkgY3JpdGVyaW9uIHdlIG5lZWQgZm9yIHNlZ21lbnRhdGlvbiBpcyB0aGUgcHJvcGVuc2l0eSB0byBwdXJjaGFzZSB0aGUgb2ZmZXJpbmcuIEhvd2V2ZXIsIHNpbmNlIHdlIGNhbm5vdCBtZWFzdXJlIHRoaXMgYXR0cmlidXRlLCB3ZSB1c2UgdmFyaWFibGVzIHRoYXQgYXJlIHBsYXVzaWJsZSBpbmRpY2F0b3JzIG9mIHRoaXMgcHJvcGVuc2l0eS4NCg0KSW4gdGhlIGRpcmVjdCBtYXJrZXRpbmcgYnVzaW5lc3MsIHRoZSBtb3N0IGNvbW1vbmx5IHVzZWQgdmFyaWFibGVzIGFyZSB0aGUgUkZNIHZhcmlhYmxlczoNCg0KKiBSID0gcmVjZW5jeSwgdGltZSBzaW5jZSBsYXN0IHB1cmNoYXNlDQoqIEYgPSBmcmVxdWVuY3ksIG51bWJlciBvZiBwcmV2aW91cyBwdXJjaGFzZXMgZnJvbSB0aGUgY29tcGFueSBvdmVyIGEgcGVyaW9kDQoqIE0gPSBtb25ldGFyeSwgYW1vdW50IG9mIG1vbmV5IHNwZW50IG9uIHRoZSBjb21wYW554oCZcyBwcm9kdWN0cyBvdmVyIGEgcGVyaW9kDQoNClRoZSBhc3N1bXB0aW9uIGlzIHRoYXQgdGhlIG1vcmUgcmVjZW50IHRoZSBsYXN0IHB1cmNoYXNlLCB0aGUgbW9yZSBwcm9kdWN0cyBib3VnaHQgZnJvbSB0aGUgY29tcGFueSBpbiB0aGUgcGFzdCwgYW5kIHRoZSBtb3JlIG1vbmV5IHNwZW50IGluIHRoZSBwYXN0IGJ1eWluZyB0aGUgY29tcGFueeKAmXMgcHJvZHVjdHMsIHRoZSBtb3JlIGxpa2VseSB0aGUgY3VzdG9tZXIgaXMgdG8gcHVyY2hhc2UgdGhlIHByb2R1Y3Qgb2ZmZXJlZC4NCg0KVGhlIDE4MDAgb2JzZXJ2YXRpb25zIGluIHRoZSBkYXRhc2V0IHdlcmUgZGl2aWRlZCBpbnRvIHJlY2VuY3ksIGZyZXF1ZW5jeSwNCmFuZCBtb25ldGFyeSBjYXRlZ29yaWVzIGFzIGZvbGxvd3M6DQoNCiogUmVjZW5jeToNCg0KYGBge3IgZXZhbCA9IFRSVUUsIGVjaG8gPSBGQUxTRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoa2FibGVFeHRyYSkNCm15dGFibGVfc3ViID0gZGF0YS5mcmFtZSgNCiAgICBSZWNlbmN5ID0gYygiMOKAkzIgbW9udGhzIiwNCiAgICAgICAgICAgICAgICAgIjPigJM2IG1vbnRocyIsDQogICAgICAgICAgICAgICAgICI34oCTMTIgbW9udGhzIiwNCiAgICAgICAgICAgICAgICAgIjEzIG1vbnRocyBhbmQgdXAiDQogICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICApLA0KICAgIFJlY29kZSA9IGMoIlJjb2RlID0gMSIsDQogICAgICAgICAgICAgICJSY29kZSA9IDIiLA0KICAgICAgICAgICAgICAiUmNvZGUgPSAzIiwNCiAgICAgICAgICAgICAgIlJjb2RlID0gNCINCiAgICAgICAgICAgICAgKSkNCg0KbXl0YWJsZV9zdWIgJT4lIGthYmxlKGVzY2FwZSA9IFQpICU+JQ0KICBrYWJsZV9wYXBlcihjKCJob3ZlciIpLCBmdWxsX3dpZHRoID0gRikNCmBgYA0KDQoNCiogRnJlcXVlbmN5Og0KDQpgYGB7ciBldmFsID0gVFJVRSwgZWNobyA9IEZBTFNFLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlID0gRkFMU0V9DQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShrYWJsZUV4dHJhKQ0KbXl0YWJsZV9zdWIgPSBkYXRhLmZyYW1lKA0KICAgIEZyZXF1ZW5jeSA9IGMoIjEgYm9vayIsDQogICAgICAgICAgICAgICAgICIyIGJvb2tzIiwNCiAgICAgICAgICAgICAgICAgIjMgYm9va3MiKSwNCiAgICBSZWNvZGUgPSBjKCJGY29kZSA9IDEiLA0KICAgICAgICAgICAgICAiRmNvZGUgPSAyIiwNCiAgICAgICAgICAgICAgIkZjb2RlID0gMyIpKQ0KDQpteXRhYmxlX3N1YiAlPiUga2FibGUoZXNjYXBlID0gVCkgJT4lDQogIGthYmxlX3BhcGVyKGMoImhvdmVyIiksIGZ1bGxfd2lkdGggPSBGKQ0KYGBgDQoNCiogTW9uZXRhcnk6DQoNCmBgYHtyIGV2YWwgPSBUUlVFLCBlY2hvID0gRkFMU0UsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0NCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpteXRhYmxlX3N1YiA9IGRhdGEuZnJhbWUoDQogICAgTW9uZXRhcnkgPSBjKCIwIOKAkyAyNSIsDQogICAgICAgICAgICAgICAgICIyNuKAkzUwIiwNCiAgICAgICAgICAgICAgICAgIjUx4oCTMTAwIiwNCiAgICAgICAgICAgICAgICAgIjEwMeKAkzIwMCIsDQogICAgICAgICAgICAgICAgICIyMDEgYW5kIHVwIg0KICAgICAgICAgICAgICAgICApLA0KICAgIFJlY29kZSA9IGMoIk1jb2RlID0gMSIsDQogICAgICAgICAgICAgICJNY29kZSA9IDIiLA0KICAgICAgICAgICAgICAiTWNvZGUgPSAzIiwNCiAgICAgICAgICAgICAgIk1jb2RlID0gNCIsDQogICAgICAgICAgICAgICJNY29kZSA9IDUiKSkNCg0KbXl0YWJsZV9zdWIgJT4lIGthYmxlKGVzY2FwZSA9IFQpICU+JQ0KICBrYWJsZV9wYXBlcihjKCJob3ZlciIpLCBmdWxsX3dpZHRoID0gRikgJT4lIA0KICBmb290bm90ZShnZW5lcmFsID0gIk1vbnRhcnkgdmFsdWVzIGFyZSBkZW5vdGVkIGluIFVTRCIsDQogICAgICAgICAgIGdlbmVyYWxfdGl0bGUgPSAiTm90ZTogIiwgDQogICAgICAgICAgIGZvb3Rub3RlX2FzX2NodW5rID0gVCwgdGl0bGVfZm9ybWF0ID0gYygiaXRhbGljIikNCiAgICAgICAgICAgKSANCmBgYA0KDQojIyBBc3NpZ25tZW50DQoNCmBgYHtyLGVjaG89RkFMU0UsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShyZXNoYXBlMikNCmxpYnJhcnkodGlkeXIpDQpsaWJyYXJ5KGdncGxvdDIpDQpkYXRhIDwtIHJlYWQuY3N2KCJkYXRhL0NoYXJsZXNCb29rQ2x1Yi5jc3YiKQ0KYGBgDQoNCipQYXJ0aXRpb24gdGhlIGRhdGEgaW50byB0cmFpbmluZyAoNjAlKSBhbmQgdmFsaWRhdGlvbiAoNDAlKS4gVXNlIHNlZWQgPSAxLioNCg0KV2hhdCBpcyB0aGUgcmVzcG9uc2UgcmF0ZSBmb3IgdGhlIHRyYWluaW5nIGRhdGEgY3VzdG9tZXJzIHRha2VuIGFzIGEgd2hvbGU/IFdoYXQgaXMgdGhlIHJlc3BvbnNlIHJhdGUgZm9yIGVhY2ggb2YgdGhlIDTDlzXDlzMgPSA2MCBjb21iaW5hdGlvbnMgb2YgUkZNIGNhdGVnb3JpZXM/IFdoaWNoIGNvbWJpbmF0aW9ucyBoYXZlIHJlc3BvbnNlIHJhdGVzIGluIHRoZSB0cmFpbmluZw0KZGF0YSB0aGF0IGFyZSBhYm92ZSB0aGUgb3ZlcmFsbCByZXNwb25zZSBpbiB0aGUgdHJhaW5pbmcgZGF0YT8NCg0KQmVmb3JlIHdlIGFuc3dlciB0aGUgcXVlc3Rpb24sIGxldCB1cyBleHBsb3JlIHRoZSBkYXRhIHNldDoNCg0KYGBge3IsZWNobz1GQUxTRX0NCmdsaW1wc2UoZGF0YSkNCmBgYA0KRmlyc3QsIHdlIHdpbGwgY29udmVydCB0aGUgcmVzcG9uc2UgdmFyaWFibGUgdG8gZmFjdG9yIGNsYXNzLg0KDQpgYGB7cix3YXJuaW5nPUZBTFNFLG1lc3NhZ2U9RkFMU0V9DQpkYXRhJFllc19GbG9yZW5jZSA8LSBmYWN0b3IoZGF0YSRZZXNfRmxvcmVuY2UsIGxhYmVscyA9IGMoIk5vIiwiWWVzIiksbGV2ZWxzID0gYygwOjEpKQ0KZGF0YSROb19GbG9yZW5jZSA8LWZhY3RvcihkYXRhJE5vX0Zsb3JlbmNlLCBsYWJlbHMgPSBjKCJObyIsIlllcyIpLGxldmVscyA9IGMoMDoxKSkNCmBgYA0KDQpTZWNvbmQsIHdlIHdvdWxkIG5lZWQgdG8gY29tcHV0ZSBSRk0gc2NvcmUgYnkgbWVyZ2luZyB0aHJlZSBzY29yZXMgaW50byBvbmUgY2VsbC4NCg0KYGBge3Isd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQ0KIyBDYWxjdWxhdGluZyBSRk0gc2NvcmUNCmRhdGEkUkZNX3Njb3JlPC0gcGFzdGUoZGF0YSRSY29kZSxkYXRhJEZjb2RlLGRhdGEkTWNvZGUpDQpkYXRhJFJGTV9zY29yZSA8LSBnc3ViKCIgIiwiIixkYXRhJFJGTV9zY29yZSkNCmRhdGEkUkZNX3Njb3JlPC1hcy5mYWN0b3IoZGF0YSRSRk1fc2NvcmUpDQpkYXRhWzE6MTAsIlJGTV9zY29yZSJdDQpgYGANCg0KTm93IHdlIHdpbGwgcHJvY2VlZCB3aXRoIGRhdGEgcGFydGl0aW9uLiBGb3IgdGhhdCB3ZSB3aWxsIHVzZSBgc2FtcGxlKClgIGZ1bmN0aW9uLCB3aGVyZSB3ZSBpbmRpY2F0ZSByb3duYW1lcyBhbmQgdGhlIG51bWJlciBvZiByYW5kb21seSBzZWxlY3RlZCByb3cgbnVtYmVycyB3ZSB3YW50IHRvIHNlcGFyYXRlIGZyb20gdGhlIHJlbWFpbmluZyBkYXRhIHNldC4NCkluIG91ciBjYXNlLCBvdXQgb2YgNDAwMCByb3dzIChDdXN0b21lcnMpLCB3ZSB3aWxsIHJhbmRvbWx5IGFzc2lnbiAyODAwIHRvIHRyYWluIGRhdGEgc2V0LCBhbmQgdGhlIHJlc3QgdG8gdGVzdCBkYXRhLg0KDQpgYGB7cn0NCiMgVHJhaW4gZGF0YQ0Kc2V0LnNlZWQoMSkNCnRyYWluSW5kZXggPC0gY2FyZXQ6OmNyZWF0ZURhdGFQYXJ0aXRpb24oZGF0YSRGbG9yZW5jZSwgcCA9IC43LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaXN0ID0gRkFMU0UsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpbWVzID0gMSkNCnRyYWluX2RhdGEgPC0gZGF0YVsgdHJhaW5JbmRleCxdDQpgYGANCg0KDQpUaGUgcmVtYWluaW5nIHBhcnQgb2YgdGhlIGRhdGEgc2V0IHdpbGwgYmUgYXNzaWduZWQgdG8gdGhlIHZhbGlkYXRpb24gZGF0YSBzZXQuDQoNCmBgYHtyfQ0KIyBWYWxpZGF0aW9uIHNldA0KdmFsaWRhdGlvbl9kYXRhICA8LSBkYXRhWy10cmFpbkluZGV4LF0NCmBgYA0KDQoNCiMjIFZpc2F1bCBleHBsb3JhdGlvbiANCg0KSXQgd291bGQgYmUgYmVuZWZpY2lhbCB0byBpbnNwZWN0IHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHJlY2VuY3ksIGZyZXF1ZW5jeSBhbmQgbW9uZXRhcnkgdmFsdWUgaW4gdGhlIHdob2xlIGRhdGEgc2V0IGJlZm9yZSB3ZSBjb250aW51ZS4gQSBxdWl0ZSBjb252ZW5pZW50IHdheSB0byBkbyBpdCBpcyBhIGhlYXRtYXAuIFRoZXJlIHdlIGNhbiBwbG90IGFsbCB0aHJlZSB2YXJpYWJsZXMgYXQgdGhlIHNhbWUgdGltZSBhbmQgaW5zcGVjdCB0aGUgbW9uZXRhcnkgdmFsdWUgKD0gYW1vdW50IHNwZW5kIGluIHRoZSB0aW1lIGZyYW1lIG9ic2VydmVkKSBvZiBlYWNoIGN1c3RvbWVyIGJhc2VkIG9uIGhpcy9oZXIgZnJlcXVlbmN5IGFuZCByZWNlbmN5Lg0KDQpgYGB7cixlY2hvPUZBTFNFfQ0Kc2V0LnNlZWQoMSkNCihkYXRhICU+JQ0KICByZW5hbWUoUmV2ZW51ZT1NKSAlPiUNCiAgZ2dwbG90KGFlcyh4PWFzLmZhY3RvcihGY29kZSkseT1hcy5mYWN0b3IoUmNvZGUpLGZpbGw9UmV2ZW51ZSkpKw0KICBnZW9tX3RpbGUoKSsNCiAgbGFicyh4PSJGcmVxdWVuY3kiLHk9IlJlY2VuY3kiLHRpdGxlID0gIldoaWNoIEN1c3RvbWVycyBBcmUgQmlnZ2VzdCBSZXZlbnVlIENvbnRyaWJ1dG9ycz8iLA0KICAgICAgIHN1YnRpdGxlID0gIkEgaGVhdG1hcCBkZXBpY3RpbmcgYSByZWxhdGlvbnNoaXAgYmV0d2VlbiByZWNlbmN5LGZyZXF1ZW5jeSBhbmQgbW9uZXRhcnkgdmFsdWVzIikrDQogIHNjYWxlX2ZpbGxfZ3JhZGllbnQobG93PSJ3aGl0ZSIsIGhpZ2g9InJlZCIpKw0KICBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscz0gYygiMSBib29rIiwiMiBib29rcyIsIjMgYm9va3MiKSkrDQogIHNjYWxlX3lfZGlzY3JldGUobGFiZWxzPWMoIjDigJMyIG1vbnRocyIsDQogICAgICAgICAgICAgICAgICIz4oCTNiBtb250aHMiLA0KICAgICAgICAgICAgICAgICAiN+KAkzEyIG1vbnRocyIsDQogICAgICAgICAgICAgICAgICIxMyBtb250aHMgYW5kIHVwIikpKw0KICB0aGVtZV9idygpKQ0KYGBgDQoNClVuc3VwcmlzaW5nbHksIGN1c3RvbWVycyB3aG8gcHVyY2hhc2VkIG1vcmUgZnJlcXVlbnRseSBnZW5lcmF0ZWQgbW9yZSByZXZlbnVlIGNvbXBhcmVkIHRvIHRob3NlIHdobyB2aXNpdGVkIGxlc3MgZnJlcXVlbnRseS4gSG93ZXZlciwgY3VzdG9tZXJzIHdobyBzcGVudCB0aGUgbW9zdCBhcmUgbm90IHRoZSBtb3N0IHJlY2VudCBvbmVzICg9IGxhc3QgcHVyY2hhc2UgaW4gMC0yKTogdGhlIGhlYXZpZXN0IHNwZW5kZXJzIGFyZSB0aGUgZnJlcXVlbnQgb25lcywgYnV0IHRoZXkgaGF2ZW4ndCBtYWRlIGEgcHVyY2hhc2UgMyB0byA2IG1vbnRocy4gVXN1YWxseSwgdGhlIGN1c3RvbWVycyB3aG8gdmlzaXRlZCBpbiB0aGUgcmVjZW50IHBhc3QgKDAtMiBtb250aHMpIGFyZSBtb3JlIGxpa2VseSB0byByZXR1cm4gY29tcGFyZWQgdG8gdGhvc2Ugd2hvIG1hZGUgYSBwdXJjaGFzZSBzb21lIHRpbWUgYWdvIGFzIG1vc3Qgb2YgdGhvc2UgY291bGQgcG90ZW50aWFsbHkgYmUgbG9zdCBjdXN0b21lcnMuIEFzIHN1Y2gsIGhpZ2hlciByZXZlbnVlIHdvdWxkIGJlIGFzc29jaWF0ZWQgd2l0aCBtb3N0IHJlY2VudCB2aXNpdHMsIGJ1dCB3ZSBzZWUgdGhhdCBpcyBub3QgcmVhbGx5IHRoZSBjYXNlIGhlcmUuIFRoaXMgY291bGQgYmUgcmVsYXRlZCB0byB0aGUgbmF0dXJlIG9mIGJvb2tzIGFzIHByb2R1Y3RzIGFuZCB0aGUgZmFjdCB0aGF0IHRoZXkgYXJlIG5vdCBmcmVxdWVudGx5IHB1cmNoYXNlZCBpdGVtcyBzdWNoIGFzIGRhaWx5IHByb2R1Y3RzIGZvciBpbnN0YW5jZS4gTmV2ZXJ0aGVsZXNzLCBpdCB3b3VsZCBkZWZpbml0aXZlbHkgYmUgd29ydGggdG8gY29uc2lkZXIgZ2l2aW5nIGluY2VudGl2ZXMgdG8gdGhlc2UgY3VzdG9tZXJzIHdobyBzcGVudCB0aGUgbW9zdCwgYnV0IHRoZSBjb21wYW55IGhhcyBub3QgaGVhcmQgb2YgdGhlbSBmb3IgMy02IG1vbnRocy4NCg0KQSBsZWdpdCBxdWVzdGlvbiBmb3IgYmV0dGVyIHVuZGVyc3RhbmRpbmcgb2Ygb3VyIHRhcmdldCBncm91cCB3b3VsZCBiZSBhYm91dCB0aGUgZGlmZmVyZW5jZSBpbiByZWNlbmN5IG9mIGN1c3RvbWVycyB3aG8gcmVzcG9uZGVkIHRvIGFkdmVydGlzaW5nIG9mICJGbG9yZW5jZSIuDQoNCmBgYHtyfQ0KZGF0YSAlPiUNCiAgcmVuYW1lKFJldmVudWU9TSwNCiAgICAgICAgIFJlc3BvbnNlPVllc19GbG9yZW5jZSkgJT4lDQogIGdncGxvdChhZXMoeD1SZXNwb25zZSx5PUYsZmlsbD1SZXNwb25zZSkpKw0KICBnZW9tX2JveHBsb3QoKSsNCiAgbGFicyh4PSJSZXNwb25zZSB0byBDYW1wYWlnbiIseT0iRnJlcXVlbmN5IChOdW1iZXIgb2YgUHVyY2hhc2VzKSIsdGl0bGUgPSAiSG93IEZyZXF1ZW50IEFyZSBDdXN0b21lcnMgSW50ZXJlc3RlZCBpbiAnRmxvcmVuY2UnPyIsDQogICAgICAgc3VidGl0bGUgPSAiQm94cGxvdHMgZGVwaWN0aW5nIGZyZXF1ZW5jeSBvZiByZXNwb25kZW50cyBhbmQgbm9uLXJlc3BvbmRlbnRzIikrDQogIHN0YXRfc3VtbWFyeShmdW4ueT1tZWFuLCBnZW9tPSJwb2ludCIsIHNoYXBlPTIwLCBzaXplPTEwLCBjb2xvcj0icmVkIiwgZmlsbD0icmVkIikgKw0KICB0aGVtZV9idygpDQpgYGANCg0KDQpUaGVyZSBzZWVtIHRvIGJlIGRpZmZlcmVuY2UgaW4gbWVhbnMgb2YgdGhlIHR3byBncm91cHMuIE5vbi1yZXNwb25kZW50cyBhcmUgc29tZXdoYXQgbGVzcyBmcmVxdWVudCBjdXN0b21lcnMsIHdoaWxlIHJlc3BvbmRlbnRzIGJlbG9uZyB0byBtb3JlIGZyZXF1ZW50IGN1c3RvbWVycy4gTGV0IHVzIHNlZSBhYm91dCB0aGVpciByZWNlbmN5Lg0KDQpgYGB7cix3YXJuaW5nPUZBTFNFLGVjaG89RkFMU0UsbWVzc2FnZT1GQUxTRX0NCmRhdGEgJT4lDQogIHJlbmFtZShSZXZlbnVlPU0sDQogICAgICAgICBSZXNwb25zZT1ZZXNfRmxvcmVuY2UpICU+JQ0KICBnZ3Bsb3QoYWVzKHg9UmVzcG9uc2UseT1SLGZpbGw9UmVzcG9uc2UpKSsNCiAgZ2VvbV9ib3hwbG90KCkrDQogIGxhYnMoeD0iUmVzcG9uc2UgdG8gQ2FtcGFpZ24iLHk9IlJlY2VuY3kobnVtYmVyIG9mICIsdGl0bGUgPSAiSG93IFJlY2VudCBBcmUgQ3VzdG9tZXJzIEludGVyZXN0ZWQgaW4gJ0Zsb3JlbmNlJz8iLA0KICAgICAgIHN1YnRpdGxlID0gIkJveHBsb3RzIGRlcGljdGluZyByZWNlbmN5IG9mIHJlc3BvbmRlbnRzIGFuZCBub24tcmVzcG9uZGVudHMiKSsNCiAgc3RhdF9zdW1tYXJ5KGZ1bi55PW1lYW4sIGdlb209InBvaW50Iiwgc2hhcGU9MjAsIHNpemU9MTAsIGNvbG9yPSJyZWQiLCBmaWxsPSJyZWQiKSArDQogIHRoZW1lX2J3KCkNCmBgYA0KDQpDdXN0b21lcnMgd2hvIHJlc3BvbmRlZCB0byB0aGUgIkZsb3JlbmNlIiBjYW1wYWlnbiBoYXZlIG9uIGF2ZXJhZ2UgbG93ZXIgcmVjZW5jeSB0aGFuIGN1c3RvbWVycyB3aG8gZGlkIG5vdCByZXNwb25kLiBBbGwgaW4gYWxsLCB3ZSBjYW4gY29uY2x1ZGUgdGhhdCBjdXN0b21lcnMgd2hvIHJlc3BvbmRlZCB0byB0aGUgY2FtcGFpZ24gYXJlLCBvbiBhdmVyYWdlLCBzbGlnaHRseSBtb3JlIGZyZXF1ZW50IGFuZCByZWNlbnQgdGhhbiBjdXN0b21lcnMgd2hvIGRpZCBub3QgcmVzcG9uZCB0byBjYW1wYWlnbi4NCg0KDQojIyBSZXNwb25zZSByYXRlIGluIHRyYWluaW5nIGRhdGENCg0KTm93IHdlIGNhbiBzdGFydCB3aXRoIGFkZHJlc3NpbmcgdGhlIGZpcnN0IHF1ZXN0aW9uOiB3aGF0IGlzIHRoZSByZXNwb25zZSByYXRlIGZvciB0cmFpbmluZyBkYXRhIGN1c3RvbWVycyB0YWtlbiBhcyB3aG9sZT8NCg0KTGV0IHVzIG5vdyBpbnNwZWN0IHRoZSByZXNwb25zZSByYXRlIGZvciB0cmFpbmluZyBkYXRhIGN1c3RvbWVycy4NCg0KYGBge3Isd2FybmluZz1GQUxTRSxlY2hvPUZBTFNFLG1lc3NhZ2U9RkFMU0V9DQpzZXQuc2VlZCgxKQ0KYXMuZGF0YS5mcmFtZSh0YWJsZSh0cmFpbl9kYXRhJFllc19GbG9yZW5jZSkpICU+JQ0KICByZW5hbWUoUmVzcG9uc2U9VmFyMSwgQ291bnQ9RnJlcSkgJT4lDQogIGdncGxvdChhZXMocmVvcmRlcihSZXNwb25zZSwtQ291bnQpLENvdW50LGZpbGw9UmVzcG9uc2UpKSArDQogIGdlb21fYmFyKHN0YXQgPSJpZGVudGl0eSIpKw0KICBsYWJzKHg9IiIsdGl0bGUgPSAiUmVzcG9uc2UgcmF0ZSB0byBOZXcgRmxvcmVuY2UgYm9vayBvZmZlciIsc3VidGl0bGUgPSAiQ3VzdG9tZXJzIGZyb20gdHJhaW5pbmcgZGF0YSIpKw0KICBnZW9tX3RleHQoYWVzKHk9KENvdW50KSxsYWJlbD1yb3VuZCgoQ291bnQvMjgwMCksMikpKSsNCiAgdGhlbWVfYncoKQ0KYGBgDQoNCg0KUmVzcG9uc2UgcmF0ZSBvZiBjdXN0b21lcnMgZnJvbSB0aGUgdHJhaW5pbmcgZGF0YSBpcyAqKmFyb3VuZCA5JS4qKg0KYGBge3Isd2FybmluZz1GQUxTRSxlY2hvPUZBTFNFLG1lc3NhZ2U9RkFMU0V9DQooUlJfdHJhaW4gPC0gbWVhbih0cmFpbl9kYXRhJEZsb3JlbmNlID09IDEpKQ0KYGBgDQoNCg0KIyMgUHJlZGljdGlvbiBvZiByZXNwb25zZSByYXRlIC0gc2luZ2xlIGNsdXN0ZXINCg0KTm93IHdlIHdvdWxkIG5lZWQgdG8gaW5zcGVjdCByZXNwb25zZSByYXRlcyBmcm9tIGFsbCA2MCAocG9zc2libGUpIGNvbWJpbmF0aW9ucywgYW5kIGNvbXBhcmUgdGhlbSB3aXRoIHRoZSBvdmVyYWxsIG9uZS4NCg0KYGBge3Isd2FybmluZz1GQUxTRSxlY2hvPUZBTFNFLG1lc3NhZ2U9RkFMU0V9DQpzZXQuc2VlZCgxKQ0KIyBMZXZlbHMNCnRyYWluX1JGTV9sZXZlbHM8LWxldmVscyhhcy5mYWN0b3IodHJhaW5fZGF0YSRSRk1fc2NvcmUpKQ0KDQojIFJGTSBzY29yZXMgYW5kIGNvcnJlc3BvbmRpbmcgcmVzcG9uc2UgcmF0ZXMNCnRyYWluX2RhdGEgJT4lDQogIGdyb3VwX2J5KFJGTV9zY29yZSkgJT4lDQogIHN1bW1hcmlzZShSZXNwb25zZV9yYXRlPXN1bShGbG9yZW5jZSkvbGVuZ3RoKEZsb3JlbmNlKSkNCmBgYA0KDQoNClRoZSBmb2xsb3dpbmcgUkZNIHNjb3JlcyBpbmRpY2F0ZSByZXNwb25zZSByYXRlIGhpZ2hlciB0aGFuIDklOg0KDQpgYGB7cix3YXJuaW5nPUZBTFNFLGVjaG89RkFMU0UsbWVzc2FnZT1GQUxTRX0NCnNldC5zZWVkKDEpDQooUlJfYWJvdmVfOTwtdHJhaW5fZGF0YSAlPiUNCiAgZ3JvdXBfYnkoUkZNX3Njb3JlKSAlPiUNCiAgc3VtbWFyaXNlKFJlc3BvbnNlX3JhdGU9c3VtKEZsb3JlbmNlKS9sZW5ndGgoRmxvcmVuY2UpLA0KICAgICAgICAgICAgbj1uKCkpICU+JQ0KICBmaWx0ZXIoUmVzcG9uc2VfcmF0ZT5SUl90cmFpbikpDQpgYGANCg0KDQpTdXBwb3NlIHRoYXQgd2UgZGVjaWRlIHRvIHNlbmQgcHJvbW90aW9uYWwgbWFpbCBvbmx5IHRvIHRoZSDigJxhYm92ZSBhdmVyYWdl4oCdIFJGTSBjb21iaW5hdGlvbnMgaWRlbnRpZmllZCBpbiBwYXJ0LiBOb3cgd2Ugc2hvdWxkIGNvbXB1dGUgdGhlIHJlc3BvbnNlIHJhdGUgaW4gdGhlIHZhbGlkYXRpb24gZGF0YSB1c2luZyB0aGVzZSAiYWJvdmUgYXZlcmFnZSIgY29tYmluYXRpb25zLg0KDQoNCmBgYHtyLHdhcm5pbmc9RkFMU0UsZWNobz1GQUxTRSxtZXNzYWdlPUZBTFNFfQ0Kc2V0LnNlZWQoMSkNCiMgVmFsaWRhdGlvbiBkYXRhIHNldCBqdXN0IHdpdGggUkZNIHNjb3JlcyB3aXRoIHRoZSBjb252ZXJzaW9uIHJhdGUgYWJvdmUgOSUNCnNvbDIgPC0gdmFsaWRhdGlvbl9kYXRhICU+JSANCiAgaW5uZXJfam9pbihSUl9hYm92ZV85LGJ5PSJSRk1fc2NvcmUiKQ0KDQojIENvbXBhcmlzb24gb2YgdGhlIHByZWRpY3RlZCBhbmQgdHJ1ZSByZXNwb25zZSByYXRlcw0Kc29sMiAlPiUNCiAgbXV0YXRlKFRydWVfUlI9bWVhbihGbG9yZW5jZT09MSkpJT4lDQogIHJlbmFtZShQcmVkaWN0ZWRfUlI9UmVzcG9uc2VfcmF0ZSklPiUNCiAgc2VsZWN0KFByZWRpY3RlZF9SUixUcnVlX1JSKSU+JQ0KICBzdW1tYXJpc2UoQXZnX1ByZWRpY3RlZF9SUj1tZWFuKFByZWRpY3RlZF9SUiksDQogICAgICAgICAgICBBdmdfVHJ1ZV9SUj1tZWFuKFRydWVfUlIpKSU+JQ0KICBtZWx0KCklPiUNCiAgZ2dwbG90KGFlcyhyZW9yZGVyKHZhcmlhYmxlLC12YWx1ZSksdmFsdWUsZmlsbD12YXJpYWJsZSkpKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikrDQogIGdlb21fdGV4dChhZXMoeT12YWx1ZSxsYWJlbD1yb3VuZCh2YWx1ZSwzKSkpKw0KICBsYWJzKHRpdGxlID0gIlJGTSBQcmVkaWN0aW9uIFBvd2VyIiwNCiAgICAgICBzdWJ0aXRsZSA9ICJQcmVkaWN0ZWQgQXZlcmFnZSBSZXNwb25zZSBSYXRlIEhpZ2hlciBUaGFuIHRoZSBUcnVlIE9uZSIsDQogICAgICAgeD0iIiwNCiAgICAgICB5PSJBdmVyYWdlIFJlc3BvbnNlIFJhdGUiKSsNCiAgc2NhbGVfZmlsbF9kaXNjcmV0ZShsYWJlbHMgPSBjKCJQcmVkaWN0ZWQgUmVzcG9uc2UgUmF0ZSIsICJUcnVlIFJlc3BvbnNlIFJhdGUiKSkrDQogIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzPSBjKCJQcmVkaWN0ZWQgUmVzcG9uc2UgUmF0ZSIsIlRydWUgUmVzcG9uc2UgUmF0ZSIpKSsNCiAgdGhlbWVfYncoKSsNCiAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKQ0KYGBgDQoNClByZWRpY3RlZCByZXNwb25zZSByYXRlIGZyb20gdGhlIHRyYWluaW5nIGRhdGEgc2V0IGlzIHNhaWQgdG8gYmUgYXQgYXJvdW5kIDE1LjElLCB3aGlsZSB0aGUgdHJ1ZSByZXNwb25zZSByYXRlIHRha2VuIGZyb20gdmFsaWRhdGlvbiBzZXQgaXMgYXQgMTAuMyUuIEZyb20gY29tcGFyaXNvbiBvZiB0cnVlIHJlc3BvbnNlIHJhdGUgaW4gdGhlIHZhbGlkYXRpb24gZGF0YSBzZXQgYW5kIHRoZSBwcmVkaWN0ZWQgcmVzcG9uc2UgcmF0ZSBmcm9tIHRoZSB0cmFpbmluZyBkYXRhIHNldCBpdCBzZWVtcyB0aGF0IHRoZSBsYXR0ZXIgcHJldHR5IG11Y2ggZGV2aWF0ZXMgZnJvbSB0aGUgZm9ybWVyLiBJbiB0aGUgbmV4dCBzZWN0aW9uIHdlIHdpbGwgc3BsaXQgY3VzdG9tZXJzIGZyb20gdGhlIHRyYWluaW5nIHNldCBpbnRvIDMgY2x1c3RlcnMsIGFuZCBhZ2FpbiBjb21wYXJlIHByZWRpY3RlZCB3aXRoIHRoZSB0cnVlIHJlc3BvbnNlIHJhdGUuDQoNCg0KIyMgUHJlZGljdGlvbiBvZiByZXNwb25zZSByYXRlIC0gMyBjbHVzdGVycw0KDQpMZXQgdXMgbm93IHNlZ21lbnQgb3VyIGN1c3RvbWVycyBpbiAzIGRpZmZlcmVudCBzZWdtZW50czoNCg0KKiBTZWdtZW50IDE6IFJGTSBjb21iaW5hdGlvbnMgdGhhdCBoYXZlIHJlc3BvbnNlIHJhdGVzIHRoYXQgZXhjZWVkIHR3aWNlIHRoZSBvdmVyYWxsIHJlc3BvbnNlIHJhdGUNCiogU2VnbWVudCAyOiBSRk0gY29tYmluYXRpb25zIHRoYXQgZXhjZWVkIHRoZSBvdmVyYWxsIHJlc3BvbnNlIHJhdGUgYnV0IGRvIG5vdCBleGNlZWQgdHdpY2UgdGhhdCByYXRlDQoqIFNlZ21lbnQgMzogdGhlIHJlbWFpbmluZyBSRk0gY29tYmluYXRpb25zDQoNCmBgYHtyLHdhcm5pbmc9RkFMU0UsZWNobz1GQUxTRSxtZXNzYWdlPUZBTFNFfQ0Kc2V0LnNlZWQoMSkNCihzZWdtZW50czM8LXRyYWluX2RhdGEgJT4lDQogIGdyb3VwX2J5KFJGTV9zY29yZSkgJT4lDQogIHN1bW1hcmlzZShSZXNwb25zZV9yYXRlPXN1bShGbG9yZW5jZSkvbGVuZ3RoKEZsb3JlbmNlKSklPiUNCiAgbXV0YXRlKENsdXN0ZXI9aWZlbHNlKFJlc3BvbnNlX3JhdGU+KDIqUlJfdHJhaW4pLCJSUl9Ud2ljZSIsDQogICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoUmVzcG9uc2VfcmF0ZT5SUl90cmFpbiAmIFJlc3BvbnNlX3JhdGU8KDIqUlJfdHJhaW4pLCJSUl9BYm92ZSIsIlJSX0JlbG93IikpKSkNCmBgYA0KDQpXZSBjbGFzc2lmaWVkIFJGTSBzY29yZXMgYmFzZWQgb24gcmVzcG9uc2UgcmF0ZSBpbnRvIDMgc2VnbWVudHMuIE5vdyB3ZSB3aWxsIGlkZW50aWZ5IGN1c3RvbWVycyB3aXRoIHRob3NlIFJGTSBzY29yZXMgaW4gdGhlIHZhbGlkYXRpb24gc2V0LCBhbmQgY29tcGFyZSBwcmVkaWN0ZWQvZXhwZWN0ZWQgcmVzcG9uc2UgcmF0ZSBmcm9tIHRoZSB0cmFpbmluZyBkYXRhIHNldCB3aXRoIHRoZSBhY3R1YWwgcmVzcG9uc2UgcmF0ZS4NCg0KYGBge3Isd2FybmluZz1GQUxTRSxlY2hvPUZBTFNFLG1lc3NhZ2U9RkFMU0V9DQpzZXQuc2VlZCgxKQ0KIyBDb21wYXJpc29uIG9mIHRoZSBwcmVkaWN0ZWQgYW5kIHRydWUgcmVzcG9uc2UgcmF0ZXMNCnZhbGlkYXRpb25fZGF0YSAlPiUgDQogIGlubmVyX2pvaW4oc2VnbWVudHMzLGJ5PSJSRk1fc2NvcmUiKSAlPiUNCiAgZ3JvdXBfYnkoUkZNX3Njb3JlKSAlPiUNCiAgbXV0YXRlKFRydWVfUlI9bWVhbihGbG9yZW5jZT09MSkpJT4lDQogIHJlbmFtZShQcmVkaWN0ZWRfUlI9UmVzcG9uc2VfcmF0ZSklPiUNCiAgc2VsZWN0KENsdXN0ZXIsUHJlZGljdGVkX1JSLFRydWVfUlIpJT4lDQogIGdyb3VwX2J5KENsdXN0ZXIpJT4lDQogIHN1bW1hcmlzZShBdmdfUHJlZGljdGVkX1JSPW1lYW4oUHJlZGljdGVkX1JSKSwNCiAgICAgICAgICAgIEF2Z19UcnVlX1JSPW1lYW4oVHJ1ZV9SUikpICU+JQ0KICBtdXRhdGUoQ2x1c3Rlcj1hcy5mYWN0b3IoQ2x1c3RlcikpJT4lDQogIHJlc2hhcGUyOjptZWx0KGlkLnZhcnM9IkNsdXN0ZXIiKSU+JQ0KICBnZ3Bsb3QoYWVzKHJlb3JkZXIodmFyaWFibGUsLXZhbHVlKSx2YWx1ZSxmaWxsPXZhcmlhYmxlKSkrDQogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikrDQogIGdlb21fdGV4dChhZXMoeT12YWx1ZSxsYWJlbD1yb3VuZCh2YWx1ZSwzKSkpKw0KICBsYWJzKHRpdGxlID0gIlJGTSBQcmVkaWN0aW9uIFBvd2VyIiwNCiAgICAgICBzdWJ0aXRsZSA9ICJQcmVkaWN0ZWQgQXZlcmFnZSBSZXNwb25zZSBSYXRlIEhpZ2hlciBUaGFuIHRoZSBUcnVlIE9uZSIsDQogICAgICAgeD0iIiwNCiAgICAgICB5PSJBdmVyYWdlIFJlc3BvbnNlIFJhdGUiKSsNCiAgc2NhbGVfZmlsbF9kaXNjcmV0ZShsYWJlbHMgPSBjKCJQcmVkaWN0ZWQgUmVzcG9uc2UgUmF0ZSIsICJUcnVlIFJlc3BvbnNlIFJhdGUiKSkrDQogIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzPSBjKCJQcmVkaWN0ZWQgUmVzcG9uc2UgUmF0ZSIsIlRydWUgUmVzcG9uc2UgUmF0ZSIpKSsNCiAgdGhlbWVfYncoKSsNCiAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSsNCiAgZmFjZXRfZ3JpZChDbHVzdGVyfi4pDQpgYGANCg0KQnkgdmlzdWFsIGluc3BlY3Rpb24gd2UgY291bGQgc2VlIHRoYXQgcHJlZGljdGVkIHJlc3BvbnNlIHJhdGVzIGFyZSBhYm92ZSB0aGUgdHJ1ZSBvbmVzIGluIHR3byBvdXQgb2YgMyBzZWdtZW50cy4NCkN1c3RvbWVycyB3aG8gaGFkIHJlc3BvbnNlIHJhdGUgYXQgbGVhc3QgdHdpY2UgdGhlIGluaXRpYWwgcmVzcG9uc2UgcmF0ZSAoOSUpIHdlcmUgZXhwZWN0ZWQgdG8gaGF2ZSBhcm91bmQgMjUlIHJlc3BvbnNlIHJhdGUsIGJ1dCB0aGUgdHJ1ZSByZXNwb25zZSByYXRlIGlzIGF0IHNpZ25pZmljYW50bHkgbG93ZXIgOS41JSByZXNwb25zZSByYXRlLiBTaW1pbGFybHksIHRoZSBSRk0gcHJlZGljdGlvbiBpbiBjYXNlIG9mIGN1c3RvbWVycyB3aG8gaGFkIHJlc3BvbnNlIHJhdGUgYmV0d2VlbiA5IGFuZCAxOCUgd2FzIHNsaWdodGx5IGluYWNjdXJhdGUgYXMgd2VsbChwcmVkaWN0ZWQgMTQuMSAlIHZzIDEwLjQlIHRydWUgcmVzcG9uc2UgcmF0ZSkuIFRoZSBvbmx5IGNhc2Ugd2hlcmUgdHJ1ZSByZXNwb25zZSByYXRlIGV4Y2VlZGVkIHRoZSBwcmVkaWN0ZWQgcmVzcG9uc2UgcmF0ZSBpcyB0aGUgc2VnbWVudCB3aXRoIHRoZSByZXNwb25zZSByYXRlIGJlbG93IDklLg0KDQpIb3dldmVyLCBpbiBvcmRlciB0byBmaW5kIG91dCBob3cgd2VsbCBvdXIgbW9kZWwgcGVyZm9ybWVkLCB3ZSB3aWxsIGNyZWF0ZSBhIGdhaW4gY2hhcnQuIEl0IGhlbHBzIHVzIGRldGVybWluZSBob3cgZWZmZWN0aXZlbHkgd2UgY2FuIHByb2NlZWQgd2l0aCBvdXIgY2FtcGFpZ24uIFdlIGFpbSBhdCBzZWxlY3RpbmcgYSByZWxhdGl2ZWx5IHNtYWxsIG51bWJlciBvZiBjdXN0b21lcnMgYW5kIGdldHRpbmcgYSByZWxhdGl2ZWx5IGxhcmdlIHBvcnRpb24gb2YgcmVzcG9uZGVudHMuIEZvciBhIGdpdmVuIG51bWJlciBvZiBjdXN0b21lcnMgZXhwcmVzc2VkIGluIHBlcmNlbnRhZ2VzLCB0aGUgZ2FpbiBjdXJ2ZSB2YWx1ZSBvbiB0aGUgeS1heGlzIHdpbGwgc2hvd3MgdXMgaG93IG11Y2ggYmV0dGVyIHdlIGFyZSBkb2luZyBjb21wYXJlZCB0byByYW5kb20gY2hvaWNlIG9mIGN1c3RvbWVycy4NCg0KYGBge3IsZWNobz1GQUxTRSxtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9DQpzZXQuc2VlZCgxKQ0KcHJlZGljdGlvbjwtdmFsaWRhdGlvbl9kYXRhICU+JSANCiAgaW5uZXJfam9pbihzZWdtZW50czMsYnk9IlJGTV9zY29yZSIpICU+JQ0KICBncm91cF9ieShSRk1fc2NvcmUpICU+JQ0KICBtdXRhdGUoVHJ1ZV9SUj1tZWFuKEZsb3JlbmNlPT0xKSklPiUNCiAgcmVuYW1lKFByZWRpY3RlZF9SUj1SZXNwb25zZV9yYXRlKSU+JQ0KICBhcnJhbmdlKGRlc2MoUHJlZGljdGVkX1JSKSkNCmBgYA0KDQoNCmBgYHtyLGVjaG89RkFMU0V9DQpzZXQuc2VlZCgxKQ0KbGlmdCA8LSBjYXJldDo6bGlmdChyZWxldmVsKGFzLmZhY3RvcihGbG9yZW5jZSksIHJlZj0iMSIpIH4gUHJlZGljdGVkX1JSLCBkYXRhID0gcHJlZGljdGlvbikNCnBsb3RseTo6Z2dwbG90bHkoZ2dwbG90KGxpZnQpKw0KICBsYWJzKHRpdGxlID0gIkhvdyBNYW55IEN1c3RvbWVycyBTaG91bGQgV2UgVGFyZ2V0PyIsDQogICAgICAgc3VidGl0bGUgPSAiR2FpbiBjaGFydCIpKw0KICB0aGVtZV9idygpKQ0KYGBgDQoNCkJhc2VkIG9uIHRoZSBnYWluIGNoYXJ0LCBwcmVkaWN0aW9uIGJhc2VkIG9uIG91ciBSRk0gbW9kZWwgcGVyZm9ybXMgYSBiaXQgYmV0dGVyIHRoYW4gYmFzZWxpbmUsIGkuZS4gcmFuZG9tIGd1ZXNzaW5nLiBNb3JlIHNwZWNpZmljYWxseSwgaWYgd2Ugc2VsZWN0IHRvcCAyMCUgY2FzZXMgYmFzZWQgb24gb3VyIG1vZGVsLCB3ZSB3b3VsZCBhdHRyYWN0IDI5JSBvZiB0aGUgdGFyZ2V0IGN1c3RvbWVycywgaS5lLiBjdXN0b21lcnMgd2hvIHdvdWxkIHJlc3BvbmQgdG8gb3VyIG1hcmtldGluZyBjYW1wYWlnbi4NCg0KQXMgdGhlIGdyYXBoIGlzIGludGVyYWN0aXZlLCB5b3UgYXJlIGFibGUgdG8gY2hlY2sgYW55IG90aGVyIHBlcmNlbnRhZ2UgeW91IHdpc2guIEFsdGhvdWdoIHdlIG1hbmFnZWQgdG8gY3JlYXRlIGEgbW9kZWwgdGhhdCBwZXJmb3JtcyBiZXR0ZXIgdGhhbiBqdXN0IGd1ZXNzaW5nLCBpbiB0aGUgZnVydGhlciBhbmFseXNlcyB3ZSB3aWxsIHRyeSBvdXQgb3RoZXIgYXBwcm9hY2hlcywgc3VjaCBhcyBsb2dpc3RpYyByZWdyZXNzaW9uLCB0aGF0IG1heSBwcm92aWRlIHVzIGJldHRlciByZXN1bHRzLg0KDQo8L2Rpdj4gDQoNCiMjIFJlZmVyZW5jZQ0KDQoqIFNobXVlbGksIEcuLCBCcnVjZSwgUC4gQy4sIFlhaGF2LCBJLiwgUGF0ZWwsIE4uIFIuLCAmIExpY2h0ZW5kYWhsLCBLLiBDLiAoMjAxOCkuIERhdGEgbWluaW5nIGZvciBidXNpbmVzcyBhbmFseXRpY3M6IENvbmNlcHRzLCB0ZWNobmlxdWVzLCBhbmQgYXBwbGljYXRpb25zIGluIFIuDQoNCg==